package ru.kest.torrentwatchlist.parsing.nnm;

import org.apache.log4j.Logger;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import ru.kest.torrentwatchlist.dao.MovieDAO;
import ru.kest.torrentwatchlist.entity.DynamicTorrentInfo;
import ru.kest.torrentwatchlist.entity.Movie;
import ru.kest.torrentwatchlist.entity.NnmTorrent;
import ru.kest.torrentwatchlist.entity.Torrent;
import ru.kest.torrentwatchlist.parsing.ParsingException;

import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NNMScraper {

    private static Map<String, String> cookies = null;

    private static Logger logger = Logger.getLogger("NNMScraper");

    @Autowired
    private MovieDAO movieDAO;

    public boolean scrape(String url) throws ParsingException {

        try {
            Movie m = null;
            for(int i = 0; i < 3; i++) {
                try {
                    m = scrapeMovie(url);
                    break;
                }catch (HttpStatusException e) {
                    logger.warn("Error catched, but skipped: " + e.getMessage() + " " + url);
                }
            }
            if (m != null) {
                movieDAO.mergeMovieToDB(m);
                return true;
            }
        } catch (Exception e) {
            throw new ParsingException("Parsing error on url " + url, e);
        }
        return false;
    }

    public Movie scrapeMovie(String url) throws ParsingException, IOException {

        if (cookies == null) {
            cookies = login();
        }

        Document doc = Jsoup.connect(url).cookies(cookies).timeout(10000).get();

        Movie m = new Movie();
        NnmTorrent t = new NnmTorrent();
        m.setTorrents(new LinkedList<Torrent>());
        m.getTorrents().add(t);
        t.setMovie(m);

        if (!chekedByModerator(doc)) {
            logger.info("Torrent not reviewed by moderator: " + url);
            return null;
        }

        // Title
        String title = doc.getElementsByClass("maintitle").first().text();
        validate(title != null && title.length() > 0, "Can't parse title of url " + url);

        // Year
        Matcher matcher = Pattern.compile("\\(\\d{4}\\)").matcher(title);
        validate(matcher.find(), "Can't find year in title (" + title + ") of url " + url);
        int yearPosStart = matcher.start() + 1;
        int yearPosEnd = matcher.end() - 1;
        m.setYear(title.substring(yearPosStart, yearPosEnd));

        String fullName = title.substring(0, yearPosStart - 1).trim();
        m.setNameFull(fullName);

        String delimiter;
        if (fullName.contains(" / ")) {
            delimiter = " / ";

        } else {
            delimiter = "/";
        }
        String[] names = fullName.split(delimiter);

        m.setNameRus(names[0].trim());
        for(int i = 1; i < names.length; i++) {
            if (isRussian(names[i])) {
                m.setNameRus(m.getNameRus() + delimiter + names[i].trim());
            } else {
                m.setNameEng(names[i].trim());
                for(int j = i + 1; j < names.length; j++) {
                    m.setNameEng(m.getNameEng() + delimiter + names[j].trim());
                }
            }
        }

        m.setDescription(getDescription(doc));
        m.setGenre(getGenre(doc));

        m.setPosterUrl(doc.select(".postImg.postImgAligned").last().attr("title"));

        m.setKinopoiskId(getKinopoiskId(doc));

        m.setImdbId(getImdbId(doc));

        m.setFoundDate(new Date());

        t.setTitle(title);
        t.setFormat(title.substring(yearPosEnd + 1).trim());
        t.setSize(getTorrentSize(doc));
        t.setTranslation(getTranslation(doc));
        t.setUrl(url);
        t.setTorrentUrl(getTorrentUrl(doc));
        String nnmId = getNnmId(url);
        validate(nnmId != null && nnmId.length() > 0, "Can't parse nnmId for url " + url);
        t.setNnmId(nnmId);

        logger.info("Movie has been added: " + m.getNameFull());

        return m;
    }

    private boolean chekedByModerator(Document doc) {
        return doc.select(":containsOwn(Оформление проверено модератором)").size() > 0;
    }
    private boolean isRussian(String str) {
        for(int i = 0; i < str.length(); i++) {
            if(str.charAt(i) >= 0x400 && str.charAt(i) < 0x500){
                return true;
            }
        }
        return false;
    }

    private static String getNnmId(String url) {
        String[] split = url.split("\\?");
        String params = split[split.length - 1];
        String[] pairs = params.split("&");
        for (String pair : pairs) {
            final int idx = pair.indexOf("=");
            final String key = idx > 0 ? pair.substring(0, idx) : pair;
            if (key.equals("t")) {
                return pair.substring(idx + 1);
            }
        }
        return null;
    }

    private static class DocFindResult {
        String phrase;
        Elements result;

        public DocFindResult(String phrase, Elements result) {
            this.phrase = phrase;
            this.result = result;
        }
    }

    private static DocFindResult findOneOfArray(Document doc, String[] phrases) {
        for (String phrase : phrases) {
            Elements root = doc.select(":containsOwn(" + phrase + ")");
            if (root.size() > 0) {
                return new DocFindResult(phrase, root);
            }
        }
        return null;
    }

    private static String getGenre(Document doc) {
        DocFindResult findResult = findOneOfArray(doc, new String[]{"Жанр:", "Жанры"});
        if (findResult == null) return null;
        Element description = findResult.result.first().parent();

        List<Node> nodes = description.childNodes();
        for(int i = 0; i < nodes.size(); i++) {

            if (getText(nodes.get(i)).contains(findResult.phrase)) {
                return getText(nodes.get(i + 1));
            }
        }
        return null;
    }

    private static String getDescription(Document doc) {
        DocFindResult findResult = findOneOfArray(doc, new String[]{"Описание фильма:", "Описание"});
        if (findResult == null) return null;
        Element description = findResult.result.first().parent();

        List<Node> nodes = description.childNodes();
        for(int i = 0; i < nodes.size(); i++) {

            if (getText(nodes.get(i)).contains(findResult.phrase)) {
                for(int j = i + 1; j < nodes.size(); j++) {
                    String s = getText(nodes.get(j));
                    if (!StringUtils.isEmpty(s)) {
                        return s;
                    }
                }
            }
        }
        return null;
    }

    private static String getTranslation(Document doc) {
        DocFindResult findResult = findOneOfArray(doc, new String[]{"Перевод:", "Перевод 1:"});
        if (findResult == null) return null;
        Element description = findResult.result.first().parent();


        List<Node> nodes = description.childNodes();
        for(int i = 0; i < nodes.size(); i++) {

            if (getText(nodes.get(i)).contains(findResult.phrase)) {
                if (nodes.size() > i + 1) {
                    return getText(nodes.get(i + 1));
                }
            }
        }
        return null;
    }

    private static String getTorrentSize(Document doc) {
        Element description = doc.select(":containsOwn(Размер:)").last().parent();

        List<Node> nodes = description.childNodes();
        for(int i = 0; i < nodes.size(); i++) {

            if (getText(nodes.get(i)).contains("Размер:")) {
                for(int j = i + 1; j < nodes.size(); j++) {
                    String s = getText(nodes.get(j));
                    if (s.matches(".*\\d.*")) {
                        int pos = s.indexOf('(');
                        if (pos < 0) {
                            return s;
                        } else {
                            return s.substring(0, pos - 1).trim();
                        }
                    }
                }
            }
        }
        return null;
    }

    private static String getKinopoiskId(Document doc) {
        Elements links = doc.select("a[href*=kinopoisk.ru]");
        for(Element el : links) {
            String[] arr = el.attr("href").split("/");
            for(int i = arr.length; i > 0; i--) {
                if (isNumeric(arr[i - 1])) {
                    return arr[i - 1];
                }
            }
        }
        return null;
    }

    private static String getImdbId(Document doc) {
        Elements links = doc.select("a[href*=imdb.com]");
        for(Element el : links) {
            String[] arr = el.attr("href").split("/");
            for(int i = arr.length; i > 0; i--) {
                if (arr[i - 1].startsWith("tt")) {
                    return arr[i - 1];
                }
            }
        }
        return null;
    }

    private static String getTorrentUrl(Document doc) {
        Element link = doc.select("a[href^=download.php]").first();
        if (link != null) {
            return "http://nnm-club.me/forum/" + link.attributes().get("href");
        }
        return null;
    }

    public static boolean isNumeric(String str) {
        try {
            int i = Integer.parseUnsignedInt(str);
        } catch(NumberFormatException nfe) {
            return false;
        }
        return true;
    }

    private static String getText(Node node) {
        if (node instanceof Element) {
            return ((Element) node).text().trim();
        } else if (node instanceof TextNode) {
            return ((TextNode) node).text().trim();
        } else {
            return node.toString();
        }
    }
    private static Map<String, String> login() throws IOException {
        Connection.Response res = Jsoup.connect("http://nnm-club.me/forum/login.php")
                .data("username", "kest01", "password", "1q2w3e")
                .data("autologin", "checked", "login", "Вход")
                .method(Connection.Method.POST)
                .timeout(5000)
                .execute();

        return res.cookies();
    }

    private static void validate(boolean condition) throws ParsingException {
        validate(condition, "Parsing error");
    }

    private static void validate(boolean condition, String message) throws ParsingException {
        if (!condition) {
            throw new ParsingException(message);
        }
    }

    public DynamicTorrentInfo parseDynamicInfo(String url) {
        DynamicTorrentInfo result = null;
        try {
            for(int i = 0; i < 3; i++) {
                try {
                    result = scrapeDynamicInfo(url);
                    break;
                }catch (HttpStatusException e) {
                    logger.warn("Error catched, but skipped: " + e.getMessage() + " " + url);
                }
            }
        } catch (Exception e) {
            logger.warn("parseDynamicInfo error on url " + url, e);
        }
        return result;
    }

    public DynamicTorrentInfo scrapeDynamicInfo(String url) throws IOException {
        if (cookies == null) {
            cookies = login();
        }

        Document doc = Jsoup.connect(url).cookies(cookies).timeout(10000).get();

        DynamicTorrentInfo result = new DynamicTorrentInfo();

        result.setSeeders(getSeeders(doc));
        result.setLeechers(getLeechers(doc));

        return result;
    }

    private String getSeeders(Document doc) {
        Elements elements = doc.select(".seed");

        for(int i = 0; i < elements.size(); i++) {
            if (getText(elements.get(i)).contains("Seeders:")) {
                if (elements.size() > i + 1) {
                    Element elem = elements.get(i + 1);
                    if (elem.childNodeSize() == 3) {
                        String unparsedText = getText(elem.childNode(1));
                        return getFirstNumFromString(unparsedText);
                    } else {
                        return getText(elem);
                    }
                }
            }
        }
        return null;
    }

    private String getLeechers(Document doc) {
        Elements elements = doc.select(".leech");

        for(int i = 0; i < elements.size(); i++) {
            if (getText(elements.get(i)).contains("Leechers:")) {
                if (elements.size() > i + 1) {
                    Element elem = elements.get(i + 1);
                    if (elem.childNodeSize() == 3) {
                        String unparsedText = getText(elem.childNode(1));
                        return getFirstNumFromString(unparsedText);
                    } else {
                        return getText(elem);
                    }
                }
            }
        }
        return null;
    }

    String getFirstNumFromString(String s) {
        Pattern p = Pattern.compile("\\d+");
        Matcher m = p.matcher(s);
        if (m.find()) {
            return m.group();
        } else
            return s;
    }


}
